/*
@STI LICENSE
(C) 2006-2008 Universit�t Innsbruck.

This file is part of myOntology.

myOntologyis free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

myOntologyis distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with myOntology.  If not, see <http://www.gnu.org/licenses/>.

@STI LICENSE */
package controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.TreeMap;

import model.DHTMLConstants;
import model.RankedElement;
import model.Vocabulary;

import org.apache.log4j.Logger;
import org.openrdf.model.URI;

import view.HTMLUIcomponents.AbstractComponent;
import controller.elementMapper.ClassMapper;
import controller.elementMapper.Mapper;
import controller.elementMapper.OntologyModuleMapper;
import controller.elementMapper.PropertyMapper;
import controller.sesameDataConnector.AbstractDataConnector;

public class ElementRanker {
	
	public static Logger logger = Logger.getLogger(ElementRanker.class);
	
	// ArrayLists for fast access
	private static java.util.ArrayList<RankedElement> ontologyModuleList = null;
	private static java.util.ArrayList<RankedElement> classList = null;
	private static java.util.ArrayList<RankedElement> propertyList = null;

	// perhaps it is better to write the following values into the database, 
	//so that the time can be determined and doesn�t depend on random (when Server starts first)
	private static java.util.Date LASTSTANDARTIZATIONDATE_OM;
	private static java.util.Date LASTSTANDARTIZATIONDATE_CLASS;
	private static java.util.Date LASTSTANDARTIZATIONDATE_PROPERTY;
	
	private static final int MAXLENGHT = 500; // # of elements in the tagcloud
	private static final double ALPHA = 0.8; // The value to multiply the values when norming them
	

	public static boolean ClearLists() {
		if( ontologyModuleList != null ) {
			ontologyModuleList.clear();
			ontologyModuleList = null;
		}

		if( classList != null ) {
			classList.clear();
			classList = null;
		}

		if( propertyList != null ) {
			propertyList.clear();
			propertyList = null;
		}
		return true;
	}
	
	public static boolean Init() {
		
		if( ontologyModuleList == null ) {
			
			logger.info("initOntologyModule");
			
			ontologyModuleList = new ArrayList<RankedElement>();
			ontologyModuleList.ensureCapacity( MAXLENGHT+1 );

			LASTSTANDARTIZATIONDATE_OM  = new GregorianCalendar().getTime();
			
			ArrayList<URI> tmp = OntologyModuleMapper.selectAll();
			if( tmp!=null ) {
				Iterator<URI> iter = tmp.iterator();
				while( iter.hasNext() ) {
					ImportElement( iter.next(), Vocabulary.ONTOLOGYMODULE );
				}
			}
		}
		
		if( classList == null ) {
			classList = new ArrayList<RankedElement>();
			classList.ensureCapacity( MAXLENGHT+1 );

			LASTSTANDARTIZATIONDATE_CLASS = new GregorianCalendar().getTime();
			
			ArrayList<URI> tmp = ClassMapper.selectAll();
			if( tmp!=null ) {
				Iterator<URI> iter = tmp.iterator();
				while( iter.hasNext() ) {
					ImportElement( iter.next(), Vocabulary.CLASS );
				}
			}			
		}
		
		if( propertyList == null ) {
			propertyList = new ArrayList<RankedElement>();
			propertyList.ensureCapacity( MAXLENGHT+1 );

			LASTSTANDARTIZATIONDATE_PROPERTY = new GregorianCalendar().getTime();
			
			ArrayList<URI> tmp = PropertyMapper.selectAll();
			if( tmp!=null ) {
				Iterator<URI> iter = tmp.iterator();
				while( iter.hasNext() ) {
					URI uri = iter.next();
					ImportElement( uri, AbstractDataConnector.getType(uri) );
				}
			}			
		}
		
		
		return true;		
	}
	
	
	/**
	 * Remove an URI out of the rankedElement-list.
	 * @param uri
	 * @return
	 */
	public static boolean RemoveUri( URI uri, URI type ) {
		boolean ret = false;
		
		java.util.ArrayList<RankedElement> list = getList( type );	

		ListIterator<RankedElement> iter = list.listIterator();

		while( iter.hasNext() ) {
					
			if( (iter.next()).getUri().equals( uri ) ) {
								
				iter.remove();
				ret = true;
				
				break; // break out of while-loop
			}
		}
		
		return ret;	
	}
	

	public static boolean ImportElement( URI uri, URI type ) {
		
		
		boolean ret = false;
		
		
		java.util.ArrayList<RankedElement> list = getList(type);	

		ListIterator<RankedElement> iter = list.listIterator();

		RankedElement el = null;
		
		// find the element (important ones will be at beginning and so get found fast):
		boolean found = false; 
		while( iter.hasNext() ) {
					
			el = iter.next();
			
			if( el.getUri().equals( uri ) ) {
				found = true;
				break; // break out of while-loop
			}
		}

		if( found == true ) {
			if( el.Standardize( ALPHA ) )
				el.StoreElement();
		}
		else {
			
			
			el = new RankedElement( uri, type );
			
			if( el.Standardize( ALPHA ) ) {

				
				el.StoreElement();
			}
			
			iter.add( el ); // insert element between actual and next value; pointer is now on next value (the new one)

		}
		

		// TODO: improve performance!
		// instantiate a new iterator because of ConcurrentModificationException (http://java.sun.com/j2se/1.5.0/docs/api/java/util/ConcurrentModificationException.html)
		ListIterator<RankedElement> iter2 = list.listIterator();
		while( iter2.hasNext() ) {
			
			el = iter2.next();
			
			if( el.getUri().equals( uri ) ) {
				break; // break out of while-loop
			}
		}
		
		
		// add the element to the list, at position just before others with the same value:
		if( iter2.hasPrevious() ) {
			// bring the list in correct order for good performance:

			RankedElement prev = iter2.previous();

			if( prev.getValue() <= el.getValue() ) {

				//printList( list );

				iter2.remove(); // remove actual element to insert it in a new position (is searched in while-loop)
				if( iter2.hasPrevious() ) {
					
					prev = iter2.previous();
					
					while( iter2.hasPrevious() && prev.getValue()<=el.getValue() ) {
						prev = iter2.previous();
					}
				}

				
				if( prev.getValue()>el.getValue() )
					iter2.next();

				// insert the actual item
				iter2.add( el ); // insert element between actual and next value; pointer is now on next value (the new one)

				// printList( list );

				// if the added value increased the list over MAXLENGHT, the unimportent ones should be cut off the list
				while( list.size() > MAXLENGHT ) {
					
					/*
					 * The elements in the lists are only to access them fast. The value is stored here AND in the DB. If the element
					 * can only be found in the DB, so the value should be as it is and should be standardized when loaded again in this list.
					 * 
					 * So: do NOT change the value of the element, when removing out of the list!
					// mark the element as new in the database:
					RankedElement last = list.get( MAXLENGHT );
					last.setValue( RankedElement.MINVALUE );
					last.StoreElement();
					*/
					
					// remove from list:
					list.remove( MAXLENGHT );
				}
			}
			// else: the list is in correct order						
		}	
		return ret;	
	}
	
	
	
	
	/**
	 * Raises the value of an element.
	 * @param uri	The URI of the element
	 * @return		if it was successfully raised f�r the given uri.
	 */
	public static boolean RaiseValueOfElement( URI uri ) {
		
		// 1. find the element by uri
		// 2. increase its relevancy
		// 3. bring the list in correct order for fast respond of 100 most relevant items.
		
		// 3. can be done with a treeMap - it automatically sorts the elements by key
		
		boolean ret = true;
		
		if( uri == null )
			return false;
			
		
		URI type = AbstractDataConnector.getType(uri);
		
		java.util.ArrayList<RankedElement> list = getList(type);	

		ListIterator<RankedElement> iter = list.listIterator();

		RankedElement el = null;
		
		// find the element (important ones will be at beginning and so get found fast):
		boolean found = false; 
		while( iter.hasNext() ) {
					
			el = iter.next();
			
			if( el.getUri().equals( uri ) ) {
				found = true;
				break; // break out of while-loop
			}
		}

		if( found == true ) {

			el.Standardize( ALPHA );
			el.increaseValue( 1.0 );

			// store the element on harddisk:
			el.StoreElement();
		}
		else {

			el = new RankedElement( uri, type ); // loads also the value of the database

			// value perhaps has to be standardized:
			el.Standardize( ALPHA );
			el.increaseValue( 1.0 );

			// store the element on harddisk:
			el.StoreElement();
			
			iter.add( el ); // insert element between actual and next value; pointer is now on next value (the new one)
		}

		// TODO: improve performance!
		// instantiate a new iterator because of ConcurrentModificationException (http://java.sun.com/j2se/1.5.0/docs/api/java/util/ConcurrentModificationException.html)
		ListIterator<RankedElement> iter2 = list.listIterator();
		while( iter2.hasNext() ) {
			
			el = iter2.next();
			
			if( el.getUri().equals( uri ) ) {
				break; // break out of while-loop
			}
		}
		
		
		// add the element to the list, at position just before others with the same value:
		if( iter2.hasPrevious() ) {
			// bring the list in correct order for good performance:

			RankedElement prev = iter2.previous();

			if( prev.getValue() <= el.getValue() ) {

				//printList( list );

				iter2.remove(); // remove actual element to insert it in a new position (is searched in while-loop)
				if( iter2.hasPrevious() ) {
					
					prev = iter2.previous();
					
					while( iter2.hasPrevious() && prev.getValue()<=el.getValue() ) {
						prev = iter2.previous();
					}
				}

				
				if( prev.getValue()>el.getValue() )
					iter2.next();

				// insert the actual item
				iter2.add( el ); // insert element between actual and next value; pointer is now on next value (the new one)

				// printList( list );

				// if the added value increased the list over MAXLENGHT, the unimportent ones should be cut off the list
				while( list.size() > MAXLENGHT ) {
					
					/*
					 * The elements in the lists are only to access them fast. The value is stored here AND in the DB. If the element
					 * can only be found in the DB, so the value should be as it is and should be standardized when loaded again in this list.
					 * 
					 * So: do NOT change the value of the element, when removing out of the list!
					// mark the element as new in the database:
					RankedElement last = list.get( MAXLENGHT );
					last.setValue( RankedElement.MINVALUE );
					last.StoreElement();
					*/
					
					// remove from list:
					list.remove( MAXLENGHT );
				}
			}
			// else: the list is in correct order						
		}	

		// printList( list );

		standartizeAllElements( type );
		
		return ret;
	}
	
	
	/*
	 * Sets the values of all Elements in the List to the minimal value. Notice that the elements NOT in the list (the ones not loaded and so still in the database) are not reset!
	 * 
	 */	
	public static boolean reset() {
		
		boolean ret = true;
		
		Iterator<URI> iter;

		try {
			ArrayList<URI> tmp = OntologyModuleMapper.selectAll();
			if( tmp!=null ) {
				iter = tmp.iterator();
				while( iter.hasNext() ) {
					URI uri = iter.next();
		
					if( !AbstractDataConnector.deleteProperty(uri, Vocabulary.RELEVANCY ) )
						ret = false;
					if( !AbstractDataConnector.insertProperty(uri, Vocabulary.RELEVANCY, RankedElement.MINVALUE.toString()) )
						ret = false;
				}
			}
			
			tmp = ClassMapper.selectAll();
			if( tmp!=null ) {
				iter = tmp.iterator();
				while( iter.hasNext() ) {
					URI uri = iter.next();
		
					if( !AbstractDataConnector.deleteProperty(uri, Vocabulary.RELEVANCY ) )
						ret = false;
					if( !AbstractDataConnector.insertProperty(uri, Vocabulary.RELEVANCY, RankedElement.MINVALUE.toString()) )
						ret = false;
				}
			}
			tmp = PropertyMapper.selectAll();
			if( tmp!=null ) {	
				iter = tmp.iterator();
				while( iter.hasNext() ) {
					URI uri = iter.next();
		
					if( !AbstractDataConnector.deleteProperty(uri, Vocabulary.RELEVANCY ) )
						ret = false;
					if( !AbstractDataConnector.insertProperty(uri, Vocabulary.RELEVANCY, RankedElement.MINVALUE.toString()) )
						ret = false;
				}
			}
		} catch (Exception e) {
			
			
			ret = false;
		}

		if( ret == true ) {
			ClearLists();
			Init();
		}
		
		return ret;
			
	}
	
	private static void standartizeAllElements( URI type ) {

		// type = null; // just to test!!!
	
		if( type != null ) {

			Date date = null;
			
			if( type.equals( Vocabulary.ONTOLOGYMODULE ) )
				date = LASTSTANDARTIZATIONDATE_OM;
			else if( type.equals( Vocabulary.CLASS ) )
				date = LASTSTANDARTIZATIONDATE_CLASS;
			else if( type.equals( Vocabulary.DATATYPEPROPERTY ) || type.equals( Vocabulary.OBJECTPROPERTY ) )
				date = LASTSTANDARTIZATIONDATE_PROPERTY;
			else {
//				System.out.println( "standartizeAllElements: ERROR: type is not known!" );
				return;
			}


			// norm the values (if older than 1 day):
			GregorianCalendar yesterday = ( new GregorianCalendar() );
			yesterday.add( GregorianCalendar.HOUR, -24 );
			

			// System.out.println( "yesterday: " + yesterday.getTime() );
			//System.out.println( "standartization: " + this.getLastStandartizationDate() );

			// get the differnece in days between yesterday and the lastStandartizationDate:
			long differenceInDays = 2; // default so that it is standartized
			
			if( date!=null ) {
					
				long differenceInMillis = yesterday.getTimeInMillis() - date.getTime();
				differenceInDays = differenceInMillis /(24*60*60*1000);
				//System.out.println( "Days difference: " + differenceInDays );
			}
			
			if( differenceInDays >= 1 ) {				
		
				java.util.ArrayList<RankedElement> list = getList(type);	
		
				ListIterator<RankedElement> iter = list.listIterator();
		
				RankedElement el = null;
				
				while( iter.hasNext() ) {
		
					el = iter.next();
					if( el.Standardize( ALPHA ) )
						el.StoreElement();
				}
				
				if( type.equals( Vocabulary.ONTOLOGYMODULE ) )
					LASTSTANDARTIZATIONDATE_OM = new GregorianCalendar().getTime();
				else if( type.equals( Vocabulary.CLASS ) )
					LASTSTANDARTIZATIONDATE_CLASS = new GregorianCalendar().getTime();
				else if( type.equals( Vocabulary.DATATYPEPROPERTY ) || type.equals( Vocabulary.OBJECTPROPERTY ) )
					LASTSTANDARTIZATIONDATE_PROPERTY = new GregorianCalendar().getTime();
			}
			
		}
		
		
		
	}
	
	
	
	
	
	/**
	 * Returns a list with the most important URIs sorted by importance (first most)
	 * @param type	URI of the type of the elements which should be in the list
	 * @param count	The maximum amount of elements in the list
	 * @return	sorted list of most important URIs
	 *//*
	private static ArrayList<RankedElement> GetImportantUris( URI type, int count ) {
		ArrayList<RankedElement> ret = new ArrayList<RankedElement>();

		if( type == null ) {
			ret = null;
		} 
		else {
			// select only elements of specific type:
			
			ListIterator iter = getList(type).listIterator();
			
			RankedElement el;
			
			for( int i = 0; i < count && iter.hasNext(); i++ ) {

				el = (RankedElement)iter.next();
				
				if( el.getType().equals( type ) ) {		
					ret.add( el );			
				}
			}
		}
		
		return ret;		
	}*/
	
	
	/**
	 * Returns a list with the most important URIs sorted by alphabet
	 * @param type	URI of the type of the elements which should be in the list
	 * @param count	The maximum amount of elements in the list
	 * @return	sorted list of most important URIs
	 */
	public static ArrayList<RankedElement> GetImportantUris_alphabeticallySorted( String viewType, int count, String lang ) {
		ArrayList<RankedElement> ret = new ArrayList<RankedElement>();

		// sort the elements in the list:
		// (if RankedElement is comparable, it could be sorted)
		
		TreeMap<String, RankedElement> map = new TreeMap<String, RankedElement>();
		
		java.util.ArrayList<RankedElement> list = getList( viewType );
		
		Iterator<RankedElement> it = list.iterator();		
		int i = 0;
		while (it.hasNext() && i++ < count ) {
			RankedElement val = it.next();

			try {
				String label = Mapper.selectLabel(val.getUri(), lang);
				
				String comment = Mapper.selectComment( val.getUri(), lang);
				
				if( comment==null )
					comment = AbstractComponent.NOTHINGSELECTED;
	
				val.setLabel( label );
				val.setComment( comment );
				
				val.setHasVersionDateTime( Mapper.selectHasVersionDateTime( val.getUri() ));
	
				map.put( label, val );
			}
			catch( Exception e ) {
//				System.out.println( "NULLPOINTER - selectLabelAndComment() which is called in GetImportantUris_alphabeticallySorted() to uri '" + val.getUri() + "'; no label found to this URI" );
//				
//				e.printStackTrace();			
			}
		}
	
		// sort it alphabetically:
		Iterator<String> keys = map.keySet().iterator();
		while( keys.hasNext() ) {
			ret.add( map.get( keys.next() ) );
		}
		

		return ret;
	}
	
	
	
	/**
	 * Just to test, which elements are in the list
	 *
	 */
	private static void printList( Collection l ) {
		Iterator iter = l.iterator();

		RankedElement el = null;
		

		System.out.println( "--- begin ---" );
		
		int i = 10;
		while( iter.hasNext() && i-->0 ) {
					
			el = (RankedElement)iter.next();

			System.out.println( el.getUri().stringValue() + " value: '" + el.getValue() + "'" );
			
		}
		

		System.out.println( "--- end ---" );
	}
	
	public static java.util.ArrayList<URI> getSubList( String viewType, int count ) {

		java.util.ArrayList<URI> ret = null;
		java.util.ArrayList<RankedElement> list = null;
		


		list = getList( viewType );
		
		if( list!=null ) {
			
			ret = new ArrayList<URI>();
			
			Iterator<RankedElement> iter = list.iterator();
			int i = 0;
			while( iter.hasNext() && i++<count ) {
				ret.add( iter.next().getUri() );
			}		
		}
		
		return ret;
	}
	
	private static java.util.ArrayList<RankedElement> getList( URI type ) {
		
		java.util.ArrayList<RankedElement> list = null;
		
		if( type != null ) {
			if( type.equals( Vocabulary.ONTOLOGYMODULE ) )
				list = ontologyModuleList;
			else if( type.equals( Vocabulary.CLASS ) )
				list = classList;
			else if( type.equals( Vocabulary.DATATYPEPROPERTY ) || type.equals( Vocabulary.OBJECTPROPERTY ) )
				list = propertyList;
		}
		
		return list;
	}
	
	

	private static java.util.ArrayList<RankedElement> getList( String viewType ) {
		
		java.util.ArrayList<RankedElement> list = null;
		
		if( viewType.equals( DHTMLConstants.ONTOLOGY_VIEWTYPE ) )
			list = ontologyModuleList;
		else if( viewType.equals( DHTMLConstants.CLASS_VIEWTYPE ) )
			list = classList;
		else if( viewType.equals( DHTMLConstants.PROPERTY_VIEWTYPE ) )
			list = propertyList;


		return list;
	}
	
		
}
